﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Eco.Handles;

using CommandLists.Model;
using CommandEditor;
using CommonClasses.Model;
using CommonClasses.Logger;
using CommonClasses.Services;
using CommonClasses.Utility;
using CommonClasses;
using VMLua;
using System.IO;
using Eco.ObjectImplementation;
using Eco.Services;
using EcoExtensions.Model;
using CommandListsVerifier;
using System.Diagnostics;

namespace CommandLists.Controls
{
    public partial class CommandListsControl : UserControl
    {
        #region Constants

        private const string IdleCommandName = "Простой";
        private const string PauseCommandName = "Выдержка времени";

        #endregion

        /// <summary>
        /// Вызывается при окончании выполнения - когда закончилоась программа
        /// </summary>
        public event MethodInvoker OnProgramEnded;

        /// <summary>
        /// Вызывается при окончании выполнения - когда закончилоась программа
        /// </summary>
        public void EndProgram()
        {
            if (OnProgramEnded != null)
                OnProgramEnded();
        }

        #region Private fields
        //положение курсора (перед текущим редактируемым тактом)
        private int currentTact;
        //текущий редактируемый список команд
        private int editingList;
        //класс, отвечающий за загрузку и сохранение инфы
        private CommandListDataLoader dataLoader;
        //загружен ли конфиг?
        private bool IsConfigured = false;
        //предыдущий редактируемый такт
        private int prevTact = 0;
        private CodeGenerator codeGenerator;

        /// <summary>
        /// Верификатор
        /// </summary>
        private CLVerifier verifier;

        /// <summary>
        /// Анимируем ли при редактировании
        /// </summary>
        private bool animatingOnStep;

        /// <summary>
        /// ErrorReport последней компиляции
        /// </summary>
        private ErrorReport lastErrorReport = null;

        /// <summary>
        /// Код последней компиляции
        /// </summary>
        private string lastCode = "";


        //false - если происходит навигация по области редактирования
        //true - если происходит навигация по палитре команд
        private bool IsChoosingCommand = false;

        private ILogger logger;

        private bool isRunningF;

        /// <summary>
        /// сигнализирует о том, выполняется ли сейчас какой-либо LPMED-код.
        /// Синхронизируется с системным таймером.
        /// </summary>
        private bool IsRunning
        {
            get
            {
                return isRunningF;
            }
            set
            {
                isRunningF = value;
            }
        }
        #endregion

        /// <summary>
        /// EcoSpace, в котором работает данный контрол
        /// </summary>
        private EcoSpace ecoSpace
        {
            get
            {
                return RootRH.EcoSpace;
            }
        }

        /// <summary>
        /// Определяет режим исполнения программ.
        /// </summary>
        public CommandListsExecuteMode Mode { get; set; }

        /// <summary>
        /// Все списки команд
        /// </summary>
        private IList<CommandList> Lists
        {
            get
            {
                var ls = new List<CommandList>();
                foreach (var cl in AllListsEH.Element.GetAsIList<CommandList>().OrderBy(cl => cl.OldID))
                {
                    ls.Add(cl);
                }
                return ls;
            }
        }

        public CommandListsControl()
        {
            InitializeComponent();

            CurrentTactChanged += new CurrentTactChangedHandler(SetTactToAll);
            CurrentTactChanged += new CurrentTactChangedHandler(CursorMoved);
            EditingListChanged += new EditingListChangedHandler(SetEditingList);

            //начальный режим работы - всё сразу и на виртуальной установке
            SetMenuItemsEnableByBenchType(BenchType.Virtual);
            Mode = CommandListsExecuteMode.AllInOnce;
        }

        #region Public methods

        /// <summary>
        /// Подготовить контрол к работе
        /// </summary>
        /// <param name="ecoSpace">ECO-сервис центр</param>
        /// <param name="logger">Логгер</param>
        public void Initialize(EcoSpace ecoSpace, ILogger logger)
        {
            if (logger == null)
                throw new NullReferenceException("Logger cannot be null!");

            this.logger = logger;

            if (ecoSpace == null)
            {
                logger.WriteException(new NullReferenceException("Trying to initialize CommandListsControl with null EcoSpace!"));
                return;
            }

            //verifier = new CLVerifier(ecoSpace, logger);

            RootRH.EcoSpace = ecoSpace;

            codeGenerator = new CodeGenerator(ecoSpace);
            
            dataLoader = new CommandListDataLoader(ecoSpace);

            ecoSpace.GetEcoService<ISystemTimer>().Subscribe(CommandListsControl_OnTick);

            logger.WriteMessage(MessagePriority.Info, "CommandLists were successfully initialized with new EcoSpace");
        }

        /// <summary>
        /// Деинициализация
        /// </summary>
        public void OnExit()
        {
            codeGenerator.AsIObject().Delete();

            ecoSpace.GetEcoService<ISystemTimer>().DetachSubcribtion(CommandListsControl_OnTick);

            while (Lists.Count != 0)
            {
                var deletingList = Lists[0];
                while (deletingList.CommandSequence.Count > 0)
                {
                    var deletingCommand = deletingList.CommandSequence[0];
                    deletingList.CommandSequence.RemoveAt(0);
                    deletingCommand.AsIObject().Delete();
                }

                while (deletingList.CommandsScope.Count > 0)
                {
                    var deletingCommand = deletingList.CommandsScope[0];
                    deletingList.CommandsScope.RemoveAt(0);
                    deletingCommand.AsIObject().Delete();
                }

                deletingList.CommandSequence.Clear();
                deletingList.CommandsScope.Clear();
                deletingList.AsIObject().Delete();
            }

            // удалить параметры, так не удаляется, несколько потерявшихся параметров-сволочей на коде actual.Clone()
            var cparams = ecoSpace.GetEcoService<IOclService>().Evaluate("CommandParameter.allInstances").GetAsIList<CommandLists.Model.CommandParameter>();
            foreach(var par in cparams)
            {
                par.AsIObject().Delete();
            }

            var clUnits = ecoSpace.GetEcoService<IOclService>().Evaluate("CLCompilationUnit.allInstances").GetAsIList<CommandLists.Model.CLCompilationUnit>();
            foreach (var unit in clUnits)
            {
                unit.AsIObject().Delete();
            }

            var clBlock = ecoSpace.GetEcoService<IOclService>().Evaluate("CLInterpretationBlock.allInstances").GetAsIList<CommandLists.Model.CLInterpretationBlock>();
            foreach (var block in clBlock)
            {
                block.AsIObject().Delete();
            }

            var clWatch = ecoSpace.GetEcoService<IOclService>().Evaluate("CLWatch.allInstances").GetAsIList<CommandLists.Model.CLWatch>();
            foreach (var watch in clWatch)
            {
                watch.AsIObject().Delete();
            }

            foreach (var ctrl in EditArea.Controls)
            {
                if (ctrl is CommandListControl)
                {
                    var clControl = ctrl as CommandListControl;
                    foreach (var ctrl2 in clControl.CommandsPanel.Controls)
                    {
                        if (ctrl2 is CommandControl)
                        {
                            var cControl = ctrl2 as CommandControl;
                            cControl.Image.Image.Dispose();
                            cControl.Image.Dispose();
                            cControl.PopupMenu.Dispose();
                            cControl.Dispose();
                        }
                    }
                    
                    clControl.Dispose();
                }
            }

            GC.Collect();
        }
        /// <summary>
        /// Обновить представление списков команд.
        /// </summary>
        public void RefreshContent()
        {
            if (ecoSpace == null)
            {
                logger.WriteException(new InvalidOperationException("CommandLists must be initialized first!"));
                return;
            }

            if (AllListsEH.Element.GetAsIList<CommandList>().Count == 0)
            {
                logger.WriteException(new InvalidOperationException("There's in EcoSpace must be at least one CommandList for the CommandListsControl to work!"));
                return;
            }

            ecoSpace.GetEcoService<IUndoService>().StartTransaction();

            try
            {
                CommandImages.Images.Clear();

                //если последовательность команд пуста, то мы добавим в неё паузы
                if (Lists[0].CommandSequence.Count == 0)
                    foreach (var list in AllListsEH.Element.GetAsIList<CommandList>())
                        list.InsertEmptyTact(0);

                #region Refresh controls

                //снимаем подписку контролов на изменение их СК
                for (int i = 1; i < EditArea.Controls.Count; i++)
                    (EditArea.Controls[i] as CommandListControl).DetachFromCommandList();
                EditArea.Controls.Clear();
                //Добавляем линейку тактов
                var tactbar = new TactBar();
                tactbar.Width = EditArea.Width - 2 * (tactbar.Margin.Left + tactbar.Margin.Right + 1);
                tactbar.TactNumber = Lists[0].CommandSequence.Count;
                EditArea.Controls.Add(tactbar);


                /*var allLists = from l in AllListsEH.Element.GetAsIList<CommandList>()
                               orderby l.OldID     
                               select l;*/

                //добавляем списки команд
                var lists = AllListsEH.Element.GetAsIList<CommandList>();
                for (int i = 0; i < lists.Count; i++)
                {
                    var list = lists.Find( l => l.OldID == i.ToString());
                    var newctrl = new CommandListControl();
                    newctrl.Width = EditArea.Width - 4 * newctrl.Margin.Left;
                    newctrl.SetCommandList(list);
                    EditArea.Controls.Add(newctrl);
                }

                /*
                foreach (var list in lists )
                {
                    var newctrl = new CommandListControl();
                    newctrl.Width = EditArea.Width - 4 * newctrl.Margin.Left;
                    newctrl.SetCommandList(list);
                    EditArea.Controls.Add(newctrl);
                }*/

                #endregion

                //подписываемся на события, необходимые для поддержки скроллинга
                (EditArea.Controls[1] as CommandListControl).NeedScrollChanged
                    += new EventHandler(CommandListsControl_NeedScrollChanged);

                EditingList = 0;
                CurrentTact = 0;

                //устанавливаем размер команды
                var commandSize = 70;
                SetCommandSize(commandSize);

                //TactsScrollBar.Maximum = Lists[0].CommandSequence.Count;

                SubscribeToComParamChange();

                EditArea.Select();
            }
            catch (Exception ex)
            {
                logger.WriteException(ex);
                ecoSpace.GetEcoService<IUndoService>().RollbackTransaction();
                throw;
            }

            ecoSpace.GetEcoService<IUndoService>().CommitTransaction();
        }
        /// <summary>
        /// Получить меню для редактирования списков команд.
        /// </summary>
        /// <returns>Список элементов меню</returns>
        public IList<ToolStripItem> GetMenus()
        {
            return new List<ToolStripItem> { editDebugModeToolStripMenuItem, EditCommandListsMenuItem};
        }

        /// <summary>
        /// Загрузить настройки списков команд из файла.
        /// </summary>
        /// <param name="fileName">Имя файла</param>
        public void LoadConfigFromFile(string fileName)
        {
            if (ecoSpace == null)
            {
                logger.WriteException(new InvalidOperationException("CommandLists must be initialized first!"));
                return;
            }


            IsConfigured = false;

            try
            {
                dataLoader.LoadConfigFromFile(fileName);
            }
            catch (Exception ex)
            {
                logger.WriteException(new Exception("CommandLists: Error during loading config from file!\nFileName" +
                    fileName + "\n", ex));
            }

            RefreshContent();

            IsConfigured = true;

            EditArea.Enabled = true;
        }

        /// <summary>
        /// Загрузить программу в виде списков команд из файла.
        /// </summary>
        /// <param name="fileName">Имя файла</param>
        public void LoadProgramFromFile(string fileName)
        {
            if (ecoSpace == null)
            {
                logger.WriteException(new InvalidOperationException("CommandLists must be initialized first!"));
                return;
            }

            if (!IsConfigured) return;

            PathUtility.RestoreCurrentDirectory(ChooseBestLocality());

            ecoSpace.GetEcoService<IUndoService>().StartTransaction();
            try
            {
                dataLoader.LoadProgramFromFile(fileName);
                RefreshContent();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Ошибка при открытии файла программы!\nНеверен формат или установки не соответствуют!",
                    "Ошибка!", MessageBoxButtons.OK, MessageBoxIcon.Error);

                new Exception("CommandLists: Error during loading program from file!\nFileName" +
                    fileName + "\n", ex).LogWith(logger);

                ecoSpace.GetEcoService<IUndoService>().RollbackTransaction();

                return;
            }
            ecoSpace.GetEcoService<IUndoService>().CommitTransaction();
        }
        /// <summary>
        /// Сохранить программу в виде списков команд в файл.
        /// </summary>
        /// <param name="fileName">Имя файла</param>
        public void SaveProgramToFile(string fileName)
        {
            if (ecoSpace == null)
            {
                logger.WriteException(new InvalidOperationException("CommandLists must be initialized first!"));
                return;
            }

            if (!IsConfigured) return;

            PathUtility.RestoreCurrentDirectory(Locality.ExeFile);

            try
            {
                dataLoader.SaveProgramToFile(fileName);
            }
            catch (Exception ex)
            {
                logger.WriteException(new Exception("CommandLists: Error during saving program to file!\nFileName" +
                    fileName + "\n", ex));
            }

        }
        /// <summary>
        /// Очистить все последовательности команд.
        /// </summary>
        public void ClearProgram()
        {
            if (ecoSpace == null)
            {
                logger.WriteException(new InvalidOperationException("CommandLists must be initialized first!"));
                return;
            }

            if (!IsConfigured) return;

            CurrentTact = 0;
            var count = Lists[0].CommandSequence.Count;
            for (int i = 0; i < count; i++)
                DeleteFromAll(false);
        }

        /// <summary>
        /// Установить активность элементов по типу установки
        /// </summary>
        /// <param name="benchType">Тип установки</param>
        public void SetMenuItemsEnableByBenchType(BenchType benchType)
        {
            if (benchType == BenchType.Real)
            {
                // запрещаем анимировать во время редактирования
                if (animatingOnStep)
                {
                    animatingOnStep = false;
                    SetDebugMode(false);
                }
                editDebugModeToolStripMenuItem.Enabled = false;
            }
            else
            {
                // разрашаем анимировать во время редактирования
                editDebugModeToolStripMenuItem.Enabled = true;
            }
        }

        /// <summary>
        /// Скомпилировать программу на СК
        /// </summary>
        /// <returns>
        /// Список ошибок
        /// </returns>
        public ErrorReport CompileAll(CompilationForm compilationForm, bool verify)
        {
            Cursor.Current = Cursors.WaitCursor;            
            lastErrorReport = new ErrorReport(ecoSpace);
            compilationForm.tbStatus.Text += "Компиляция...";
            compilationForm.Update();
            lastErrorReport.AddRange(codeGenerator.GenAll(Lists, out lastCode));
            if (lastErrorReport.Errors.Count == 0)
            {
                compilationForm.tbStatus.Text += "\r\nКомпиляция прошла успешно";                
                compilationForm.Update();
                if (verify)
                {
                    compilationForm.tbStatus.Text += "\r\nВерификация...";
                    compilationForm.Update();
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    lastErrorReport.AddRange(verifier.Verify(Lists));
                    if (lastErrorReport.Errors.Count == 0)
                        compilationForm.tbStatus.Text += "\r\nВерификация прошла успешно: программа безопасна";
                    else compilationForm.tbStatus.Text += "\r\nВерификация провалена: программа опасна";
                    watch.Stop();
                    compilationForm.tbStatus.Text += "\r\nВремя верификации: " + watch.Elapsed.Seconds + " сек";
                }
            }
            else compilationForm.tbStatus.Text += "\r\nКомпиляция прервана: имеются ошибки";
            compilationForm.Update();            
            return lastErrorReport;
        }

        /// <summary>
        /// Запустить последний компиленный код
        /// </summary>
        public void ExecuteLastCompiled()
        {
            if (lastErrorReport.Errors.Count == 0)
                SendCodeToDebugger(lastCode);
            SetEditingEnabled(false);
            SetDebugMode(false);
            editDebugModeToolStripMenuItem.Enabled = false;
            Mode = CommandListsExecuteMode.AllInOnce;
        }

        /// <summary>
        /// Остановить выполнение
        /// </summary>
        public void Stop()
        {
            IsRunning = false;
            ecoSpace.GetEcoService<ISystemTimer>().Enabled = false;             
            SetEditingEnabled(true);
        }
        /// <summary>
        /// Выполнить обработку сообщения о нажатии клавиши.
        /// </summary>
        public void HandleKeyPress(object sender, KeyPressEventArgs e)
        {
            //во время выполнения мы не реагируем на все мольбы пользователя
            if (IsRunning)
                return;
            var args = new PreviewKeyDownEventArgs((Keys)e.KeyChar);
            EditArea_PreviewKeyDown(sender, args);
            e.Handled = args.IsInputKey;
        }

        #endregion

        #region Private methods

        private void RefreshToolBox()
        {
            ToolBox.Items.Clear();

            foreach (var command in Lists[EditingList].CommandsScope)
            {
                //TODO: сообщать об ошибке загрузки картинки для команды
                try
                {
                    PathUtility.RestoreCurrentDirectory(Locality.ExeFile);
                    //загружаем картинку для команды
                    CommandImages.Images.Add("CommandImg_" + command.Name,
                        new Bitmap(command.ImagePath));
                    //PathUtility.MakePathGlobal(command.ImagePath,
                    //Path.GetDirectoryName(Application.ExecutablePath))));

                    //добавляем иконку в ToolBox
                    var control = new ListViewItem(command.Name, "CommandImg_" + command.Name);
                    control.Tag = command;
                    ToolBox.Items.Add(control);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Ошибка при загрузке изображения для команды:\n" +
                        "Команда: " + command.Name + ", указанный путь: " + command.ImagePath);
                }
            }
        }

        /// <summary>
        /// Подписаться на изменение параметров у всех команд.
        /// </summary>
        private void SubscribeToComParamChange()
        {
            var commands = ecoSpace.GetEcoService<IOclService>()
                .Evaluate("Command.allInstances").GetAsIList<Command>();
            foreach (var com in commands)
            {
                com.OnParameterChange += new CommandParameterChangedHandler(SyncronizeLoopCounters);
            }
        }

        /// <summary>
        /// Получить подходящую Locality, взависимости от типа интеграции
        /// </summary>
        private Locality ChooseBestLocality()
        {
            if (PathUtility.IsCurrentProjectPathSet())
                return Locality.CurrentProjectDir;
            else return Locality.ExeFile;
            return Locality.ExeFile;
        }

        #endregion

        #region Editing

        /// <summary>
        /// Вставить такт в текущий такт в текущий список команд и в последний такт во все остальные списки
        /// </summary>
        private void InsertToCurrent()
        {
            if ((ecoSpace == null) || (!IsConfigured))
                return;

            var curlen = Lists[0].CommandSequence.Count;

            //получаем список СК, в которые необходимо вставить такт, 
            //чтобы соблюсти корректность расстановки команд
            var curlists = GetUsageScopeUnion();

            foreach (var list in Lists)
                list.InsertEmptyTact((curlists.Contains(list)) ? CurrentTact : curlen);

            (EditArea.Controls[0] as TactBar).TactNumber++;

            RethinkScrollParams();
        }

        /// <summary>
        /// Вставить такт в указанный такт во все списки команд
        /// </summary>
        /// <param name="tact">Номер такта</param>
        private void InsertToAll(int tact)
        {
            if ((ecoSpace == null) || (!IsConfigured))
                return;

            foreach (var list in Lists)
                list.InsertEmptyTact(tact);

            (EditArea.Controls[0] as TactBar).TactNumber++;

            RethinkScrollParams();
        }

        /// <summary>
        /// Удалить текущий такт из текущего СК и всех связанных с ним.
        /// </summary>
        /// <param name="needAsk">Нужно ли запрашивать разрешение на уделение</param>
        private void DeleteFromCurrent(bool needAsk)
        {
            if ((ecoSpace == null) || (!IsConfigured))
                return;

            var curlen = Lists[0].CommandSequence.Count;

            if (needAsk)
                if (MessageBox.Show("Удалить такт?", "Вы уверены?",
                    MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
                    return;

            //получаем список СК, в которые необходимо вставить такт, чтобы соблюсти корректность расстановки команд
            var curlists = GetUsageScopeUnion();

            //для выбранных мы удаляем текущий такт и добавляем по паузе в конец
            foreach (var list in curlists)
            {
                list.DeleteTact(CurrentTact);
                list.InsertEmptyTact(curlen - 1);
            }

            RethinkScrollParams();
        }

        /// <summary>
        /// Удалить текущий такт из всех СК
        /// </summary>
        /// <param name="needAsk">Нужно ли запрашивать разрешение на уделение</param>
        private void DeleteFromAll(bool needAsk)
        {
            if ((ecoSpace == null) || (!IsConfigured))
                return;

            //последний такт удалять нельзя
            if (CurrentTact == Lists[0].CommandSequence.Count - 1)
                return;

            if (needAsk)
                if (MessageBox.Show("Удалить такт?", "Вы уверены?",
                    MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
                    return;

            if (Lists[0].CommandSequence.Count > 0)
                (EditArea.Controls[0] as TactBar).TactNumber--;

            foreach (var list in Lists)
                list.DeleteTact(CurrentTact);

            RethinkScrollParams();
        }

        private List<CommandList> GetUsageScopeUnion()
        {
            var idle = Lists[EditingList].CommandsScope.Find(com => com.Name == IdleCommandName);
            var pause = Lists[EditingList].CommandsScope.Find(com => com.Name == PauseCommandName);

            //неправдали, декларативные решения на C# неважно выглядят?)
            //P.S. они, кстати, ещё и не работают изза того, 
            //  что acc.Union(com.UsageScope) as List<CommandList> = null)))

            //var i = 0;
            //return Lists[EditingList].CommandSequence
            //    .Aggregate(new List<CommandList>(), (acc, com) =>
            //        ((((i++) >= CurrentTact) && (!com.Equals(pause))) ? 
            //            acc.Union(com.UsageScope) : acc) as List<CommandList>);

            //императивное решение... ужОс
            var res = new List<CommandList>();
            for (int i = CurrentTact; i < Lists[EditingList].CommandSequence.Count; i++)
                if (!idle.Equals(Lists[EditingList].CommandSequence[i]))
                {
                    if (pause.Equals(Lists[EditingList].CommandSequence[i]))
                    {
                        if (!res.Contains(Lists[EditingList]))
                            res.Add(Lists[EditingList]);
                    }
                    else
                        foreach (var comlist in Lists[EditingList].CommandSequence[i].UsageScope)
                            if (!res.Contains(comlist))
                                res.Add(comlist);
                }
            return res;
        }

        /// <summary>
        /// Вставить команду в текущий такт в требуемые списки команд
        /// </summary>
        /// <param name="selected"></param>
        private void ApplyCommand(Command selected)
        {
            //редактирование во время исполнения не разрешается
            if (IsRunning  || animatingOnStep)
                return;

            //если команда, уже имеющаяся в списках команд идентична той, на которую мы заменяем, то мы ничгео не делаем
            if (Lists[EditingList].CommandSequence[CurrentTact].Equals(selected))
                return;

            //если команда неприменима никуда, то выходим
            if (selected.UsageScope.Count == 0)
                return;

            //найдём команду "Простой" на всякий случай ;)
            var idle = Lists[EditingList].CommandsScope.Find(com => com.Name == IdleCommandName);
            var pause = Lists[EditingList].CommandsScope.Find(com => com.Name == PauseCommandName);

            #region User request for delete

            //Выберем все команды из данного такта, которым грозит замена
            var removedcommands =
                from list in (!selected.Equals(idle) && !selected.Equals(pause)) ? //"Простой" - частный случай, её мы применяем только в текущий список
                    selected.UsageScope
                    : new ObjectListAdapter<CommandList>(new List<CommandList> { Lists[EditingList] })
                where (!list.CommandSequence[CurrentTact].Equals(idle)) //на паузы можно забить
                   && (!list.CommandSequence[CurrentTact].Equals(pause)) //на паузы можно забить
                   && (!list.CommandSequence[CurrentTact].Equals(selected)) //про команды того же типа мы не спрашиваем
                select new { List = list, Command = list.CommandSequence[CurrentTact] };

            //Выделяем команды, которым грозит удаление
            foreach (var com in removedcommands)
            {
                foreach (var list in com.Command.UsageScope)
                {
                    (EditArea.Controls[Lists.IndexOf(list) + 1] as CommandListControl)
                        .Highlight(Color.Blue, CurrentTact);

                }
            }

            //если в данном такте уже есть команды, отличные от паузы,
            //то мы должны спросить его, можно ли их заменять
            DialogResult result = DialogResult.Yes;
            if (removedcommands.Count() > 0)
            {
                //данная проверка отсеивает случаи, когда заменяемая команда одна и она встречается только в текущем СК
                if (!((removedcommands.Count() == 1)
                    && (removedcommands.ElementAt(0).Command.UsageScope.Count == 1)
                    && (removedcommands.ElementAt(0).Command.UsageScope[0] == Lists[editingList])))
                {
                    var str = "При добавлении выбранной команды следующие команды в текущем такте будут заменены:\n";
                    foreach (var com in removedcommands)
                        str += "Список команд: " + com.List.Name + ", Команда: " + com.Command.Name + ";\n";
                    result = MessageBox.Show(str, "Продолжить?", MessageBoxButtons.YesNo);
                }
                //Снимаем выделение команды, которым грозит удаление
                foreach (var com in removedcommands)
                    foreach (var list in com.Command.UsageScope)
                    {
                        (EditArea.Controls[Lists.IndexOf(list) + 1] as CommandListControl)
                            .HighLightOff(CurrentTact);
                    }
            }

            //если пользователь сказал, что заменять не надо, то и вставлять не будем
            if (result != DialogResult.Yes) return;
            #endregion

            //если пользователь сказал "заменяй", то мы должны удалить заменяемые команды
            //одновременно из всей области одновременного использования удаляемой команды
            foreach (var pair in removedcommands)
                RemoveCommand(pair.Command, CurrentTact);

            //Выдержка пяти секунд - тоже частный случай (;-))
            if (selected.Equals(pause) || selected.Equals(idle))
                Lists[EditingList].SetCommandForTact(currentTact, selected);
            else
                //добавляем выбранную команду в каждый список команд из данной области применения
                foreach (var list in selected.UsageScope)
                    list.SetCommandForTact(currentTact, selected);

            //если мы редактируем последний такт, то после него автоматически добавляется ещё один
            if (CurrentTact == Lists[0].CommandSequence.Count - 1)
                InsertToAll(CurrentTact + 1);
        }

        /// <summary>
        /// Замена данной команды Простойпи в указанном такте.
        /// </summary>
        private void RemoveCommand(Command command, int currentTact)
        {
            //найдём команду "Простой" на всякий случай ;)
            var pause = Lists[EditingList].CommandsScope.Find(com => com.Name == IdleCommandName);

            foreach (var list in Lists)
            {
                if (list.CommandSequence[currentTact].Equals(command))
                    list.SetCommandForTact(currentTact, pause);
            }
        }

        private void SetEditingEnabled(bool canEdit)
        {
            //запрещаем или разрешаем редактировать параметры команд
            for (int i = 1; i < EditArea.Controls.Count; i++)
                (EditArea.Controls[i] as CommandListControl).SetCommandParamsEditingEnabled(canEdit);
            EditCommandListsMenuItem.Enabled = canEdit;
            foreach (var item in EditCommandListsMenuItem.DropDownItems)
            {
                if (item is ToolStripMenuItem)
                    (item as ToolStripMenuItem).Enabled = canEdit;
            }
            MainSplitContainer.Panel2Collapsed = !canEdit;
        }

        private void InsertTactMenuItem_Click(object sender, EventArgs e)
        {
            InsertToAll(CurrentTact);
        }

        private void DeleteTactMenuItem_Click(object sender, EventArgs e)
        {
            DeleteFromCurrent(false);
        }

        private void DeleteAllMenuItem_Click(object sender, EventArgs e)
        {
            DeleteFromAll(true);
            SetTactToAll(CurrentTact);
        }

        private void InsertCurrentMenuItem_Click(object sender, EventArgs e)
        {
            InsertToCurrent();
        }

        private void ClearMenuItem_Click(object sender, EventArgs e)
        {
            ClearProgram();
        }

        private void ToolBox_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (ToolBox.SelectedIndices.Count == 0)
                return;

            ApplyCommand(ToolBox.SelectedItems[0].Tag as Command);
            EditArea.Focus();
        }

        private void SyncronizeLoopCounters(object sender, CommandParameterChangedHandlerArgs e)
        {
            //обработка изменения количества повторений цикла
            //(необходимо синхронизировать значения для всех команд в данном такте)
            if (e.Source.Name == "Начало цикла")
            {
                //номер такта, в котором находится отредактированная команда
                int editedTact = -1;
                bool found = false;
                foreach (var list in Lists)
                {
                    for (int i = 0; i < list.CommandSequence.Count; i++)
                        if (list.CommandSequence[i] == e.Source)
                        {
                            editedTact = i;
                            found = true;
                            break;
                        }
                    if (found)
                        break;
                }
                //выбираем все команды из этого такта
                var commandsInTact = from list in Lists
                                     select list.CommandSequence[editedTact];                

                //устанавливаем все параметры всех остальных команд в то же значение
                foreach (var com in commandsInTact)
                    if (com != e.Source)                    
                        com.Parameters.Find(par => par.Name == e.ChangedParameter.Name)
                            .DefaultValue = e.ChangedParameter.DefaultValue;

                foreach (var commandLstCtrl in EditArea.Controls)
                {
                    if (commandLstCtrl is CommandListControl)
                    {
                        CommandControl comCtrl = (commandLstCtrl as CommandListControl).CommandsPanel.Controls[editedTact] as CommandControl;
                        comCtrl.Command.RefreshParameters();
                        comCtrl.RefreshImage();
                    }
                }                
            }
        }

        #endregion

        #region Navigation

        private int CurrentTact
        {
            get
            {
                return currentTact;
            }
            set
            {
                if ((value < 0) || (value >= Lists[0].CommandSequence.Count))
                    return;

                prevTact = currentTact;
                currentTact = value;

                CurrentTactChanged(currentTact);
            }
        }

        private int EditingList
        {
            get
            {
                return editingList;
            }
            set
            {
                if ((value < 0) || (value >= Lists.Count))
                    return;

                var prevval = editingList;
                editingList = value;

                EditingListChanged(editingList, prevval);
            }
        }

        //установить текущий редактируемый такт для всех контролов
        private void SetTactToAll(int newTact)
        {
            //устанавливаем такт для линейки команд
            (EditArea.Controls[0] as TactBar).CurrentTact = newTact;
            //для списков команд
            for (int i = 1; i < EditArea.Controls.Count; i++)
                (EditArea.Controls[i] as CommandListControl).SetEditingTact(newTact);

            //при движении курсора мы пытаемся установить значение скролла таким образом, чтобы курсор находился посреди экрана
            var viscount = (EditArea.Controls[1] as CommandListControl).VisibleElementsCount;
            var tactcount = Lists[0].CommandSequence.Count;
            SetFirstVisibleTactToAll(Math.Max(0, Math.Min(CurrentTact - viscount / 2, tactcount - viscount)));

            TactsScrollBar.Value = ((newTact + 1) * TactsScrollBar.Maximum) / tactcount;
        }

        private void SetEditingList(int newList, int prevList)
        {
            //Controls[0] - панель с номерами тактов
            if (EditArea.Controls[prevList + 1] is CommandListControl)
                (EditArea.Controls[prevList + 1] as CommandListControl).SetIsEditing(false);

            if (EditArea.Controls[newList + 1] is CommandListControl)
                (EditArea.Controls[newList + 1] as CommandListControl).SetIsEditing(true);

            RefreshToolBox();
        }

        private void CursorMoved(int newTact)
        {
            if (Mode == CommandListsExecuteMode.Interpretation)
                SendCodeToInterpreter((newTact > prevTact) ? codeGenerator.GenCodeFromTact(
                    from cl in Lists select cl.CommandSequence[currentTact - 1], false, true)
                    : codeGenerator.GenBackupCode());
        }

        private void EditArea_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            if ((ecoSpace == null) || (!IsConfigured))
                return;

            //если идёт выбор команды на палитре, то выходим
            if (IsChoosingCommand && !animatingOnStep)
            {
                var args = new KeyEventArgs(e.KeyData);
                ToolBox_KeyDown(sender, args);
                e.IsInputKey = args.Handled;
                return;
            }

            //Пары вида: комбинация клавиш - действие
            var inputkeys = new Dictionary<Keys, MethodInvoker>();
            inputkeys.Add(Keys.Right, () => { CurrentTact++; });
            inputkeys.Add(Keys.Left, () => { CurrentTact--; });
            inputkeys.Add(Keys.Up, () => { EditingList--; });
            inputkeys.Add(Keys.Down, () => { EditingList++; });
            inputkeys.Add(Keys.Enter, () => //по Enter мы переходим на выбор команды
                {
                    IsChoosingCommand = true;
                    ToolBox.Focus();
                });

            if (inputkeys.Keys.Contains(e.KeyCode))
            {
                //если что-то выполняется, то мы это отлавливаем, не реалигуем и фокус никому не отдаём)))
                if (!IsRunning)
                    inputkeys[e.KeyCode]();
                e.IsInputKey = true;
            }
        }

        private void ToolBox_KeyDown(object sender, KeyEventArgs e)
        {
            //если идёт навигация по области редактирования, то выходим
            if (!IsChoosingCommand)
            {
                EditArea_PreviewKeyDown(sender, new PreviewKeyDownEventArgs(e.KeyCode));
                e.Handled = true;
                return;
            }

            var inputkeys = new Dictionary<Keys, MethodInvoker>();
            inputkeys.Add(Keys.Enter, () => //по Enter мы применяем выбранную команду и переходим в редим навигации
                {
                    if (ToolBox.SelectedItems.Count > 0)
                        ApplyCommand(ToolBox.SelectedItems[0].Tag as Command);
                    IsChoosingCommand = false;
                    EditArea.Focus();
                });
            inputkeys.Add(Keys.Escape, () => //по Escape мы переходим в режим навигации
                {
                    IsChoosingCommand = false;
                    EditArea.Focus();
                });

            if (inputkeys.Keys.Contains(e.KeyCode))
            {
                //если что-то выполняется, то мы это отлавливаем, не реалигуем и фокус никому не отдаём)))
                if (!IsRunning)
                {
                    inputkeys[e.KeyCode]();
                    e.Handled = true;
                }
            }
        }

        private void ToolBox_Enter(object sender, EventArgs e)
        {
            IsChoosingCommand = true;
            if (ToolBox.Items.Count > 0)
                ToolBox.Items[0].Selected = true;
        }

        private void ToolBox_Leave(object sender, EventArgs e)
        {
            //если фокус ушел с палитры, то мы переходим в режим навигации
            IsChoosingCommand = false;
        }

        private void CommandListsControl_Enter(object sender, EventArgs e)
        {
            EditArea.Focus();
        }

        private void CommandListsControl_Leave(object sender, EventArgs e)
        {
            //EditArea.Focus();
        }

        private void HomeMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTact = 0;
        }

        private void EndMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTact = Lists[0].CommandSequence.Count - 1;
        }

        #endregion

        #region Scroll

        private void SetFirstVisibleTactToAll(int value)
        {
            foreach (var ctrl in EditArea.Controls)
                if (ctrl is CommandListControl)
                    (ctrl as CommandListControl).FirstVisibleTact = value;
                else if (ctrl is TactBar)
                    (ctrl as TactBar).FirstVisibleTact = value;
        }

        private void RethinkScrollParams()
        {
            TactsScrollBar.Enabled = (EditArea.Controls[1] as CommandListControl).IsNeedScroll;

            var ctrl = (EditArea.Controls[1] as CommandListControl);
            TactsScrollBar.Maximum =
                (ctrl.IsNeedScroll) ?
                        Math.Max(0, ctrl.CommandList.CommandSequence.Count - ctrl.VisibleElementsCount - 1)
                    : 0;
            TactsScrollBar.LargeChange =
                (ctrl.IsNeedScroll) ?
                    TactsScrollBar.Maximum / (ctrl.CommandList.CommandSequence.Count - ctrl.VisibleElementsCount)
                : TactsScrollBar.Maximum;
        }

        private void TactsScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            var ctrl = (EditArea.Controls[1] as CommandListControl);
            SetFirstVisibleTactToAll(TactsScrollBar.Value);
            //Math.Max(
            //    0,
            //    Math.Min(
            //        TactsScrollBar.Value,
            //        Lists[0].CommandSequence.Count - ctrl.VisibleElementsCount)));
        }

        private void CommandListsControl_NeedScrollChanged(object sender, EventArgs e)
        {
            RethinkScrollParams();
        }

        #endregion

        #region Resize

        private void SetCommandSizeMenuItem_Click(object sender, EventArgs e)
        {
            SetCommandSize(AskIntegerValueForm.Ask(
                "Размер изображения", "Установите значение (в пикселях):",
                (EditArea.Controls[1] as CommandListControl).Height,
                40, 200));
        }

        private void SetCommandSize(int size)
        {
            foreach (var ctrl in EditArea.Controls)
                if (ctrl is CommandListControl)
                    (ctrl as CommandListControl).Height = size;

            (EditArea.Controls[0] as TactBar).TitleWidth =
                (EditArea.Controls[1] as CommandListControl).TitleWidth;

            (EditArea.Controls[0] as TactBar).TactWidth = size - 4;

            //чтобы списки никуда не пропадали, мы не даём настолько сжимать контрол
            EditArea.MinimumSize = new Size(
                size,
                Lists.Count * size + EditArea.Controls[0].Height + 2); //учитываем высоту TactBar'а

            /*MainSplitContainer.SplitterDistance =
            MainSplitContainer.Panel1MinSize = EditArea.MinimumSize.Height
                + EditAreaSplitContainer.SplitterWidth + TactsScrollBar.Height;
            */
            MainSplitContainer.FixedPanel = FixedPanel.Panel1;

            this.MinimumSize = new Size(
                this.MinimumSize.Width,
                MainSplitContainer.Panel1MinSize + ToolBox.MinimumSize.Height
                + MainSplitContainer.SplitterWidth
                + EditAreaSplitContainer.SplitterWidth + 2);
        }

        private void EditArea_Resize(object sender, EventArgs e)
        {
            if (EditArea.Controls.Count == 0) return;

            EditArea.Controls[0].Refresh();

            foreach (var ctrl in EditArea.Controls)
                (ctrl as Control).Width = EditArea.Width - 4 * (ctrl as Control).Margin.Left;

            RethinkScrollParams();
        }

        #endregion

        #region Execute Mode

        private void debugMenuItem_Click(object sender, EventArgs e)
        {
            SetDebugMode(!animatingOnStep);
            var luavm = ecoSpace.GetEcoService<IVirtualMachineService>();
            if (animatingOnStep)
            {
                SetEditingEnabled(false);    
                luavm.Begin();
                ecoSpace.GetEcoService<ISystemTimer>().Enabled = true; 
            }
            else
            {
                SetEditingEnabled(true);
                luavm.End();
                IsRunning = false;
                ecoSpace.GetEcoService<ISystemTimer>().Enabled = false; 
            }
        }

        /// <summary>
        /// Установить режим: интерпретация с анимацией во время редактирования или исполнение всего
        /// </summary>
        /// <param name="mode">Режим</param>
        public void SetDebugMode(bool mode)
        {
            if (mode == animatingOnStep)
                return;

            if (mode)
            {
                editDebugModeToolStripMenuItem.Text = "Режим: пошаговый";
                IsChoosingCommand = false;
                ToolBox.Enabled = false;
                animatingOnStep = true;
                CurrentTact = 0;
            }
            else
            {
                editDebugModeToolStripMenuItem.Text = "Режим: редактирование";
                ToolBox.Enabled = true;
                animatingOnStep = false;
            }

            Mode = (mode) ?
                      CommandListsExecuteMode.Interpretation
                    : CommandListsExecuteMode.AllInOnce;

            //когда включено Визуализировать мы не можем скакать через несколько тактов
            HomeMenuItem.Enabled =
            EndMenuItem.Enabled = !mode;

            if (OnModeChanged != null)
                OnModeChanged(Mode);
        }

        #endregion

        #region Debugger Connection

        private void SendCodeToDebugger(string code)
        {
            var debugger = ecoSpace.GetEcoService<IDebuggerService>();
            var luavm = ecoSpace.GetEcoService<IVirtualMachineService>();
            var translator = ecoSpace.GetEcoService<ITranslatorService>();

            luavm.Begin();

            #region To Debugger
            debugger.NewDebugSession();

            var cunit = new CLCompilationUnit(ecoSpace);
            cunit.LPMEDCode = code;
            cunit.Watches.Add(new CLWatch(ecoSpace) { Name = "CLTactCounter", Type = "int" });
            cunit.OnVariableStateChanged += new TestImplementations.Model.VariableStateChangedHandler(cunit_OnVariableStateChanged);
            var tree = debugger.CommitProgram(cunit);
            #endregion

            #region Compilation
            var compdata = new CompilationData(ecoSpace);
            compdata.Tree = new EcoObject(ecoSpace, tree);
            translator.Translate(ref compdata);
            if (compdata.CompiledCode == string.Empty)
            {
                logger.WriteMessage(MessagePriority.Error,
                    "CommandLists: GenerateCode returned string.Empty!");
                return;
            }
            #endregion

            #region To Lua
            var actor = new ActiveObject("CLFullProgramActor");
            actor.LuaProgram = compdata.CompiledCode;
            actor.ScriptOneStepCode = compdata.CoroutineCallString;
            actor.OnDone += new EventHandler(CLFullProgramActor_OnDone);
            luavm.RegisterActiveObject(actor);
            #endregion

            IsRunning = true;
            ecoSpace.GetEcoService<ISystemTimer>().Enabled = true; 
        }

        private void CLFullProgramActor_OnDone(object sender, EventArgs e)
        {
            EndProgram();
        }

        void cunit_OnVariableStateChanged(ILPMEDVariable variable)
        {
            if (variable.Name == "CLTactCounter")
                try
                {
                    CurrentTact = (int)((double)variable.Value.Object);
                }
                catch (Exception ex)
                {
                    logger.WriteException(new Exception("Error during updating CurrentTact from CLTactCounter", ex));
                }
        }

        void CommandListsControl_OnTick(object sender, EventArgs e)
        {
            if (Mode == CommandListsExecuteMode.AllInOnce)
                ecoSpace.GetEcoService<IVirtualMachineService>().ProcessOneStep();
        }

        #endregion

        #region Interpreter connection

        private void SendCodeToInterpreter(string code)
        {
            var interpreter = ecoSpace.GetEcoService<IInterpreterService>();

            if (interpreter == null)
                return;

            if (code == string.Empty)
                return;

            var block = new CLInterpretationBlock(ecoSpace);

            block.Code = code;

            InterpreterSubscribe(block);

            interpreter.DoBlock(block);
        }

        private void InterpreterSubscribe(CLInterpretationBlock block)
        {
            block.OnCompilationFailed += new CommonClasses.CompilationResultHandler(BlockCompilationFailed);
            block.OnInterpretationFinished += new EventHandler(BlockInterpretationFinished);
            block.OnInterpretationStarted += new EventHandler(BlockInterpretationStarted);
            block.OnRunTimeErrorOccured += new CommonClasses.StringMessageHandler(BlockRunTimeErrorOccured);
        }

        private void DetachInterpreterSubscribe(CLInterpretationBlock block)
        {
            block.OnCompilationFailed -= new CommonClasses.CompilationResultHandler(BlockCompilationFailed);
            block.OnInterpretationFinished -= new EventHandler(BlockInterpretationFinished);
            block.OnInterpretationStarted -= new EventHandler(BlockInterpretationStarted);
            block.OnRunTimeErrorOccured -= new CommonClasses.StringMessageHandler(BlockRunTimeErrorOccured);
        }

        private void BlockRunTimeErrorOccured(string message)
        {
            logger.WriteException(new InvalidOperationException(
                "CommandLists: RunTimeErrorOccured during execution interpretation block! " +
                message));
        }

        private void BlockInterpretationStarted(object sender, EventArgs e)
        {
            Console.WriteLine();
            Console.WriteLine("BlockInterpretationStarted");

            //var luavm = ecoSpace.GetEcoService<IVirtualMachineService>();
            //luavm.Begin();

            OnInterpreterBlockStarted(null, null);

            IsRunning = true;
        }

        private void BlockInterpretationFinished(object sender, EventArgs e)
        {
            Console.WriteLine("BlockInterpretationFinished");

            DetachInterpreterSubscribe(sender as CLInterpretationBlock);

            IsRunning = false;

            Focus();

            OnInterpreterBlockEnded(null, null);

            //var luavm = ecoSpace.GetEcoService<IVirtualMachineService>();
            //luavm.End();
        }

        private void BlockCompilationFailed(ErrorReport report)
        {
            var message = report.Errors.Aggregate(
                "Errors: ",
                (acc, err) => { return acc + "\n" + err.Message; });

            logger.WriteException(new InvalidOperationException(
                "CommandLists: Interpretation block compilation failed! " +
                message));
        }

        #endregion

        #region Delegate types
        private delegate void CurrentTactChangedHandler(int newTact);
        private delegate void EditingListChangedHandler(int newList, int prevList);
        public delegate void ModeChangedHandler(CommandListsExecuteMode mode);
        #endregion

        #region Events

        /// <summary>
        /// Вызывается при изменении текущего редактируемого такта
        /// </summary>
        private event CurrentTactChangedHandler CurrentTactChanged;
        /// <summary>
        /// Вызывается при изменении текущего редактируемого списка команд
        /// </summary>
        private event EditingListChangedHandler EditingListChanged;
        /// <summary>
        /// Вызывается при изменении режима выполнения.
        /// </summary>
        public event ModeChangedHandler OnModeChanged;

        /// <summary>
        /// Началось выполнение блока интерпертации (такта)
        /// </summary>
        public event EventHandler OnInterpreterBlockStarted;

        /// <summary>
        /// Закончилось выполнение блока интерпертации (такта)
        /// </summary>
        public event EventHandler OnInterpreterBlockEnded;

        #endregion
    }

    /// <summary>
    /// Режимы исполнения программ с использованием списков команд.
    /// </summary>
    public enum CommandListsExecuteMode
    {
        /// <summary>
        /// "Всё сразу" - компилируется сразу вся программа и целиком посылается на исполнение.
        /// </summary>
        AllInOnce,
        /// <summary>
        /// Режим интерпретации, т.е. код посылается после каждого движения курсора влево или вправо.
        /// </summary>
        Interpretation
    }
}